﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

using Microsoft.Scripting.Ast;
using System.Reflection.Emit;
using Scala.Compiler.Binding;
using Scala.Compiler.Types;
using System.Reflection;

namespace Scala.Compiler.Ast
{
    public class ValDefStatement : Statement
    {
        private readonly ModifierFlags _mods;
        private readonly string _name;
        private readonly Expression _rhs;

        public ValDefStatement(ModifierFlags mods, string name, Expression rhs)
        {
            _mods = mods;
            _name = name;
            _rhs = rhs;
        }

        public ModifierFlags Modifiers
        {
            get { return _mods; }
        }

        public String Name
        {
            get { return _name; }
        }

        public ScalaVariable ScalaVariable { get; set; }

        public PropertyBuilder PropertyBuilder { get; set; }

        public Expression Rhs
        {
            get { return _rhs; }
        }

        public override void Walk(ScalaWalker walker)
        {
            if (walker.Walk(this))
            {
                _rhs.Walk(walker);
            }
            walker.PostWalk(this);
        }

        public override Microsoft.Scripting.Ast.Expression Reduce()
        {
            Type paramType;
            InternalTypes.GetType(TypeName, Scope, out paramType);
            Microsoft.Scripting.Ast.Expression rhs = null;
            if (Rhs is SelectStatement)
            {
                Rhs.Reduce();
                Microsoft.Scripting.Ast.Expression instance = null;
                var sel = Rhs as SelectStatement;
                if (sel.Prop != null)
                {
                    instance = Expression.TypeMethodCall(
                                    sel.Prop.DeclaringType,
                                    sel.Prop.GetGetMethod(true),
                                    Type.EmptyTypes,
                                    new ParameterExpression[] { }
                                );
                }
                else if (sel.QualExpr != null)
                {
                    instance = sel.QualExpr;
                }
                if (sel.IsMeth)
                {
                    if (instance == null)
                    {
                        rhs = Expression.TypeMethodCall(sel.Meth.DeclaringType, sel.Meth, Type.EmptyTypes, new ParameterExpression[] { });
                    }
                    else
                    {
                        rhs = Expression.TypeMethodCall(instance, sel.Meth, Type.EmptyTypes, new ParameterExpression[] { });
                    }
                }
                else if (sel.IsProp)
                {
                    if (instance == null)
                    {
                        rhs = Expression.TypeMethodCall(sel.RetProp.DeclaringType, sel.RetProp.GetGetMethod(true), Type.EmptyTypes, new ParameterExpression[] { });
                    }
                    else
                    {
                        rhs = Expression.TypeMethodCall(instance, sel.RetProp.GetGetMethod(true), Type.EmptyTypes, new ParameterExpression[] { });
                    }
                }
                else
                {
                    throw new Exception(String.Format("The assignment must be a method or property"));
                }
                //if(sel.QualExpr != null)
                //{
                //    rhs = Expression.Block(sel.QualExpr, rhs);
                //}
                //else if (sel.Prop != null)
                //{
                //    //rhs = Expression.Call(rhs, sel.Prop.GetGetMethod(true));
                //    rhs = Expression.TypeMethodCall(
                //                Expression.TypeMethodCall(
                //                    sel.Prop.DeclaringType,
                //                    sel.Prop.GetGetMethod(true),
                //                    Type.EmptyTypes,
                //                    new ParameterExpression[] { }
                //                ),
                //                rhs
                //        );
                //}

                //if (ScalaVariable.IsProperty)
                //{
                //    return Expression.Block(
                //                    Expression.TypeMethodCall(
                //                            Scope.CurrentType.TypeBuilder.AsType(),
                //                            ScalaVariable.PropertyBuilder.GetSetMethod(true),
                //                            new Type[] { paramType },
                //                            rhs));
                //}
                //else
                //{
                //    ParameterExpression value = ScalaVariable.ParamExpression = (ParameterExpression)Expression.Variable(paramType, Name);
                //    return Expression.Assign(
                //                value,
                //                (!paramType.IsValueType) ? Expression.TypeAs(rhs, paramType) : Expression.Convert(rhs, paramType)
                //            );
                //}
            }
            else
            {
                rhs = Rhs.Reduce();
            }
            var valueCast = (!paramType.IsValueType) ? Expression.TypeAs(rhs, paramType) : Expression.Convert(rhs, paramType);

            if (ScalaVariable.IsProperty)
            {
                return Expression.Block(
                                    Expression.TypeMethodCall(
                                            Scope.CurrentType.TypeBuilder.AsType(),
                                            ScalaVariable.PropertyBuilder.GetSetMethod(true),
                                            new Type[] { paramType },
                                            valueCast));
            }
            else
            {
                ParameterExpression value = ScalaVariable.ParamExpression = (ParameterExpression)Expression.Variable(paramType, Name);
                return Expression.Assign(
                            value,
                            (!paramType.IsValueType) ? Expression.TypeAs(rhs, paramType) : Expression.Convert(rhs, paramType)
                        );
            }
        }
    }
}
